package com.maybach.workflow.core;


import com.maybach.util.AssertUtil;
import com.maybach.workflow.common.api.FlowChain;
import com.maybach.workflow.common.api.IContextPreparer;
import com.maybach.workflow.common.api.IFlowEngine;
import com.maybach.workflow.common.api.WorkFlowContext;
import com.maybach.workflow.common.api.enums.FlowActTypeEnum;
import com.maybach.workflow.common.api.enums.FlowStateEnum;
import com.maybach.workflow.common.api.enums.NodeTypeEnum;
import com.maybach.workflow.common.api.exception.FlowException;
import com.maybach.workflow.common.api.node.BaseNode;
import com.maybach.workflow.common.api.node.TaskNode;
import com.maybach.workflow.common.ext.*;
import com.maybach.workflow.common.model.*;
import com.maybach.workflow.core.expression.LogicalExprHelper;
import com.maybach.workflow.core.processor.ProcessorFactory;
import com.maybach.workflow.core.repository.IFlowRepository;
import com.maybach.workflow.core.service.SecurityService;
import com.maybach.workflow.core.service.TaskService;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by houenxun on 15/10/19.
 */
public class FlowEngine implements IFlowEngine {
    Logger logger = LoggerFactory.getLogger(this.getClass());
    @Autowired
    protected IFlowRepository flowRepository;

    @Autowired
    protected TaskService taskService;

    @Autowired
    protected SecurityService securityService;

    @Autowired
    private IContextPreparer contextPreparer;
    @Autowired
    private LogicalExprHelper logicalExprHelper;


    @Override
    public Long startFlow(Long bizId, String bizType) {

        WorkFlowContext context = new WorkFlowContext();
        context.setBizId(bizId);
        context.setBizType(bizType);
        contextPreparer.fillContext(context);
        for (FlowChain flowChain : FlowDefineFactory.getDefaultFlowChain()) {
            if (logicalExprHelper.judgeFairily(flowChain.getExpression(), context.getBizData())) {
                context.setThisFlowChain(flowChain);
                break;
            }
        }
        ProcessorFactory.getProcessor(NodeTypeEnum.start).runOut(context);
        return context.getThisFlow().getId();
    }

    @Override
    public Long startFlow(FlowActInfo flowActInfo) {
        AssertUtil.notNull(flowActInfo.getFlowBizId(), "flowActInfo.getFlowBizId()");
        AssertUtil.notNull(flowActInfo.getFlowBizType(), "flowActInfo.getFlowBizType()");
        flowActInfo.setActType(FlowActTypeEnum.startFlow.name());
        WorkFlowContext context = new WorkFlowContext();
        context.setBizId(flowActInfo.getFlowBizId());
        context.setBizType(flowActInfo.getFlowBizType());
        contextPreparer.fillContext(context);
        for (FlowChain flowChain : FlowDefineFactory.getDefaultFlowChain()) {
            if (logicalExprHelper.judgeFairily(flowChain.getExpression(), context.getBizData())) {
                context.setThisFlowChain(flowChain);
                break;
            }
        }
        ProcessorFactory.getProcessor(NodeTypeEnum.start).runOut(context);

        this.recordFlowAct(flowActInfo, context);
        return context.getThisFlow().getId();
    }

    @Override
    public void executeTask(Long taskId) {
        WorkFlowContext context = new WorkFlowContext();
        this.parpareContextByTask(context, taskId, true);
        contextPreparer.fillContext(context);
        ProcessorFactory.getProcessor(context.getThisNode().getNodeType()).runOut(context);

    }


    @Override
    public List<FlowTaskInfo> getTasks(Long flowId, boolean filter) {
        FlowDO flowDO = flowRepository.getFlowById(flowId);
        return this.getTasks(flowDO, filter);
    }

    @Override
    public List<FlowTaskInfo> getTasks(FlowDO flowDO, boolean filter) {
        List<FlowTaskDO> flowTaskDOs = flowRepository.getFlowTasksByFlowId(flowDO.getId());
        List<FlowTaskInfo> list = new ArrayList<FlowTaskInfo>();
        for (FlowTaskDO taskDO : flowTaskDOs) {
            List<FlowTaskActorDO> actorDOList = flowRepository.getTaskActorsByTaskId(taskDO.getId());
            if (filter) {
                if (securityService.isPermitted(null, actorDOList)) {
                    list.add(assemblyFlowTaskInfo(flowDO, taskDO, true));
                }
            } else {
                list.add(assemblyFlowTaskInfo(flowDO, taskDO, securityService.isPermitted(null, actorDOList)));
            }
        }

        return list;
    }

    private FlowTaskInfo assemblyFlowTaskInfo(FlowDO flowDO, FlowTaskDO taskDO, Boolean isPermit) {
        FlowTaskInfo flowTaskInfo = new FlowTaskInfo();
        flowTaskInfo.setFlowTaskDO(taskDO);
        flowTaskInfo.setFlowDO(flowDO);
        flowTaskInfo.setFlowChain(FlowDefineFactory.getFlowDefine(flowDO.getFlowName(), flowDO.getFlowVersion()));
        flowTaskInfo.setTaskNode((TaskNode) flowTaskInfo.getFlowChain().getBaseNode(taskDO.getTaskName()));
        flowTaskInfo.setPermit(isPermit);
        return flowTaskInfo;
    }


    @Override
    public List<FlowTaskInfo> getTasks(String flowBizType, Long bizId, boolean filterByRole) {
        List<FlowTaskInfo> result = new ArrayList<FlowTaskInfo>();
        List<FlowDO> flowList = flowRepository.getFlowByBizIdAndType(bizId, flowBizType);
        for (FlowDO flowDO : flowList) {
            result.addAll(this.getTasks(flowDO, filterByRole));
        }
        return result;
    }

    @Override
    public List<TaskNode> getNextTasks(Long flowId) {
        List<TaskNode> taskNodes = new ArrayList<TaskNode>();
        List<FlowTaskDO> taskDOList = flowRepository.getFlowTasksByFlowId(flowId);
        for (FlowTaskDO taskDO : taskDOList) {
            WorkFlowContext context = new WorkFlowContext();
            this.parpareContextByTask(context, taskDO.getId(), false);
            contextPreparer.fillContext(context);

            for (BaseNode baseNode : ProcessorFactory.getProcessor(context.getThisNode().getNodeType()).getNextNodes(context)) {
                if (baseNode instanceof TaskNode) {
                    taskNodes.add((TaskNode) baseNode);
                }
            }
        }
        return taskNodes;
    }

    @Override
    public void checkTaskAllow(Long taskId) {
        WorkFlowContext context = new WorkFlowContext();
        this.parpareContextByTask(context, taskId, true);
    }

    @Override
    public boolean isTaskAllow(Long taskId) {
        try {
            this.checkTaskAllow(taskId);
        } catch (FlowException e) {
            logger.error("", e);
            return false;
        }
        return true;
    }

    @Override
    public void operaterFlow(IFlowActInfo flowActInfo) {
        AssertUtil.notNull(flowActInfo.getFlowId(), "flowActInfo.getFlowId()");
        AssertUtil.notNull(flowActInfo.getActType(), "flowActInfo.getActType()");
        FlowActTypeEnum flowActType = FlowActTypeEnum.valueOf(flowActInfo.getActType());
        WorkFlowContext context = new WorkFlowContext();
        this.parpareContextByFlow(context, flowActInfo.getFlowId());
        contextPreparer.fillContext(context);
        switch (flowActType) {
            case startFlow:
                throw new RuntimeException("this method is not support start flow");
            case cancle:

                taskService.cancleFlow(context);
                break;
            case close:
                flowRepository.updateFlowStatus(flowActInfo.getFlowId(), FlowStateEnum.close.getStatus());
                break;
            case pause:
                flowRepository.updateFlowStatus(flowActInfo.getFlowId(), FlowStateEnum.pause.getStatus());
                break;
            case resume:
                flowRepository.updateFlowStatus(flowActInfo.getFlowId(), FlowStateEnum.init.getStatus());
                break;
        }

        this.recordFlowAct(flowActInfo, context);


    }

    private WorkFlowContext parpareContextByTask(WorkFlowContext context, Long taskId, boolean check) {
        FlowTaskDO taskDO = flowRepository.getFlowTaskById(taskId);
        if (check) {
            isTaskAllow(context, taskId, taskDO);
        }
        context.setThisTask(taskDO);
        context.setActionName(taskDO.getTaskName());
        this.parpareContextByFlow(context, taskDO.getFlowId());
        FlowChain flowChain = context.getThisFlowChain();
        BaseNode baseNode = flowChain.getBaseNode(taskDO.getTaskName());
        context.setThisNode(baseNode);

        return context;
    }

    /**
     * 当前任务是否允许被当前角色执行
     *
     * @param context
     * @param taskId
     * @param taskDO
     */
    private void isTaskAllow(WorkFlowContext context, Long taskId, FlowTaskDO taskDO) {
        if (null == taskDO) {
            FlowTaskHisDO taskHisDO = flowRepository.getFlowTaskHistByTaskId(taskId);
            throw new FlowException("任务已经被执行", null).setContext(context);
        }

        List<FlowTaskActorDO> taskActorDOList = flowRepository.getTaskActorsByTaskId(taskDO.getId());
        if (CollectionUtils.isEmpty(taskActorDOList)) {
            throw new FlowException("任务没有执行者", null).setContext(context);
        }
        boolean isAllow = false;

        for (FlowTaskActorDO taskActorDO : taskActorDOList) {
            if (this.securityService.isPermitted(context, taskActorDO)) {
                context.setTaskActor(taskActorDO);
                isAllow = true;
            }
        }

        if (!isAllow) {
            throw new FlowException("没有权限执行该任务", null).setContext(context);
        }
    }

    private WorkFlowContext parpareContextByFlow(WorkFlowContext context, Long flowId) {
        FlowDO flowDO = flowRepository.getFlowById(flowId);
        AssertUtil.notNull(flowDO, "flowDO where flowId=" + flowId);
        context.setThisFlow(flowDO);
        FlowChain flowChain = FlowDefineFactory.getFlowDefine(flowDO.getFlowName(), flowDO.getFlowVersion());
        AssertUtil.notNull(flowChain, "flowChain where flowName=" + flowDO.getFlowName() + ", flowVersion=" + flowDO.getFlowVersion());
        context.setThisFlowChain(flowChain);
        return context;
    }



    /*@Override
    @Transactional
    public void rejectByTask(Long taskId) {
        WorkFlowContext context = new WorkFlowContext();
        this.parpareContextByTask(context, taskId);
        contextPreparer.fillContext(context);
        FlowRunData.getTaskService().rejectTask(context);

    }*/

    @Override
    public void executeTask(ITaskActInfo taskActInfo) {
        AssertUtil.notNull(taskActInfo.getTaskId(), "taskActInfo.getTaskId()");
        AssertUtil.notNull(taskActInfo.getActType(), "taskActInfo.getActType()");
        AssertUtil.isTrue(taskActInfo.getActType().equals(FlowActTypeEnum.pass.name()) || taskActInfo.getActType().equals(FlowActTypeEnum.reject.name()), "flowActType=" + taskActInfo.getActType());
        WorkFlowContext context = new WorkFlowContext();
        this.parpareContextByTask(context, taskActInfo.getTaskId(), true);
        contextPreparer.fillContext(context);
        this.recordTaskAct(taskActInfo, context);
        if (taskActInfo.getActType().equals(FlowActTypeEnum.pass.name())) {
            ProcessorFactory.getProcessor(context.getThisNode().getNodeType()).runOut(context);
        } else {
            taskService.rejectTask(context);
            flowRepository.updateFlowStatus(context.getThisFlow().getId(), FlowStateEnum.reject.getStatus());
        }
    }

    private FlowActLogDO recordActInfo(IActInfo actInfo) {
        this.fillActInfo(actInfo);
        FlowActLogDO actLogDO = new FlowActLogDO();
        actLogDO.setActUserRole(actInfo.getActUserRole());
        actLogDO.setActUserName(actInfo.getActUserName());
        actLogDO.setActUserId(actInfo.getActUserId());
        actLogDO.setActType(actInfo.getActType());
        actLogDO.setMemo(actInfo.getMemo());
        actLogDO.setReasonCode(actInfo.getReasonCode());
        actLogDO.setReasonDesc(actInfo.getReasonDesc());
        actLogDO.setAttributes(actInfo.getAttribute());

        return actLogDO;
    }


    private void recordFlowAct(IFlowActInfo flowActInfo, WorkFlowContext context) {
        FlowActLogDO actLogDO = this.recordActInfo(flowActInfo);
        FlowDO flowDO = context.getThisFlow();
        actLogDO.setBizId(flowDO.getBizId());
        actLogDO.setBizType(flowDO.getBizType());
        actLogDO.setFlowId(flowDO.getId());
        actLogDO.setFlowName(flowDO.getFlowName());
        flowRepository.createFlowActLog(actLogDO);
    }


    private void recordTaskAct(ITaskActInfo taskActInfo, WorkFlowContext context) {
        FlowActLogDO actLogDO = this.recordActInfo(taskActInfo);
        FlowDO flowDO = context.getThisFlow();
        actLogDO.setBizId(flowDO.getBizId());
        actLogDO.setBizType(flowDO.getBizType());
        actLogDO.setFlowId(flowDO.getId());
        actLogDO.setFlowName(flowDO.getFlowName());


        FlowTaskDO taskDO = context.getThisTask();
        actLogDO.setTaskName(taskDO.getTaskName());
        actLogDO.setTaskId(taskDO.getId());
        flowRepository.createFlowActLog(actLogDO);
    }


    private void fillActInfo(IActInfo actInfo) {
        if (null == actInfo.getActUserId()) {
            actInfo.setActUserId(securityService.getUserId());
        }
        if (null == actInfo.getActUserName()) {
            actInfo.setActUserName(securityService.getUserName());
        }

        if (null == actInfo.getActUserRole()) {
            actInfo.setActUserRole(securityService.getUserLable());
        }

    }


}
